Skip to content

fix(recompiler): emit a GPU-selected formatted buffer access through one shared function - #2349

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
Pipodi:fix/formatted-gpu-buffer-helper
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
Pipodi:fix/formatted-gpu-buffer-helper

Conversation

@Pipodi

@Pipodi Pipodi commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

What

#1959 moves the switch over every buffer format into one function per resource when the descriptor is prepared. It keeps the inline switch for GPU-selected descriptors (memory.gpuDescriptor). For those, every buffer_load_format_* / buffer_store_format_* still emits all 77 cases inline, and each case inlines the address computation, bounds checks and BDA reads of formatted().

A title with about 300 such accesses in one compute program (Ghost of Yōtei, PPSA26344, 0x8000289300, 102 guest blocks) compiles to 37.1M SPIR-V words: 292 OpSwitches, about 700k blocks, 287 distinct block shapes. Each pipeline takes 40 s or more to create on NVIDIA, and each shader disk-cache entry is up to ~300 MB. This is probably also the 13.4M-word module reported on #1183.

  • formattedGpuCall emits one DontInline SPIR-V function per component count, direction, d16, coherence, kind and stop mode, and runs formatSwitch inside it.
    • It takes the descriptor fields (base, records, stride, word 3, swizzle, valid), the index, offset, soffset and the access's PC. Each access is one call.
  • When BDA faults stop invocations, the function returns {value, stopped} (just stopped for a store). Inside it, a stop returns that through bdaStopValue, as read_bda_span does. The caller calls StopBdaInvocationIf on the flag, so the pixel-shader interlock and OpKill handling stay in the entry function.
  • BdaInstructionPc returns the instruction's PC, or the PC parameter inside the shared function (SpirvEmitterState::bdaPcOverride). The BDA read, write, store, atomic and overflow paths use it, so a fault records the PC of the access that faulted.

Tested

Windows 11, MinGW-w64 GCC 15.2.0, RTX 4090 (driver 617.42).

  • agc_formatted_buffer_once gains a GPU-selected case: dword 3 of the V# comes from v_readfirstlane_b32 s11, v0.
  • Full ctest: 506/506 passed.
  • Ghost of Yōtei, same tree before and after:
    • 0x8000289300 goes from 37,110,613 to 1,447,831 words, and its recompile from 7.2 s to 0.25 s.
    • 0x800036e100 goes from 818,855 to 260,672 words; its pipeline now takes 2.4 s.
    • The average disk-cache entry drops below 1 MB.
    • The title reaches its first presented frames (the SIE intro card). Before, a 25-minute run was still creating the second and third 37M-word pipelines.

Checklist

🤖 Generated with Claude Code

@Kikoarl

Kikoarl commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Tested on Linux (CachyOS x86_64, Intel RPL-S iGPU / Mesa 26.3.0-devel / Vulkan 1.4.363, GCC 16.2.1) at 0690a904:

  • Build: Built cleanly in RAM (/tmp/anyps5_build) with zero warnings/errors.
  • Conventions: python3 tools/check_conventions.py --base origin/main --head HEAD -> PASS (0 errors).
  • Verification: Verified FormattedBufferOnce.cpp test and SPIR-V emission deduplication in SpirvBda.hpp. Generates cleaner SPIR-V without code bloat.

LGTM!

…one shared function

A formatted buffer load or store through a GPU-selected descriptor still
emitted the switch over every buffer format inline, at each access. A
Ghost of Yotei compute pass with ~300 such accesses compiled to 37M SPIR-V
words, and its pipeline took 40 s or more to create.

The switch is now emitted once per component count, direction, d16,
coherence, kind and stop mode, as a SPIR-V function that takes the
descriptor fields, the offsets and the access's PC. When faults stop the
invocation, the helper returns whether one did and the caller stops there.
BDA faults record the PC from BdaInstructionPc, the access's own PC or the
one a shared helper takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main: dropped the first commit (the old version of #1959, already merged as f2bd6d4) and resolved the conflicts in SpirvBdaRead.cpp (BDA byte-write functions on main now) and FormattedBufferOnce.cpp (Compile(loads, formattedStores, gpu)), plus one more BdaInstructionPc site in the BDA store probe path.

Checked on Linux (RADV) on the rebased tree: libSceAgc + libSceAgcDriver ctest green (334/335, the one failure is a stale registration not present on main). agc_formatted_buffer_once fails without the change and passes with it. The Ghost of Yōtei numbers and the NVIDIA/Windows runs are the author's and Kikoarl's (Linux, Intel); not reproduced here. Merging once CI is green on the rebased head.

@adriadam10
adriadam10 force-pushed the fix/formatted-gpu-buffer-helper branch from 0690a90 to 8dbea2c Compare October 10, 2026 08:44
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@adriadam10
adriadam10 merged commit 4b584d1 into boykopovar:main Oct 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants